/**
 * 
 */
package domain;

import interfaces.Trazo;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Observable;
import java.util.Set;

/**
 * @author De La
 * 
 */
public class Pizarra extends Observable{
	public static final byte PUNTO_VACIO = 0;
	public static final byte PUNTO_PAPA = 1;
	
	public static final byte NUMERO_TRAZO_INICIAL = 2;
	
	public static final int ANCHO = 800;
	public static final int ALTO = 600;

	private int[][] matriz = new int[ANCHO][ALTO];
	private Point ultimoPunto;
	private Integer numeroDeTrazo = NUMERO_TRAZO_INICIAL;
	
	/**
	 * Agrega un punto al trazo actual, o iniciando uno nuevo.
	 * En caso de producirce una colicion devuelve el primer punto
	 * en conflicto.
	 * 
	 * @param punto
	 * @param trazo
	 * @return
	 */
	public Point addPunto(Point punto, Trazo trazo) {
		Point puntoChoque = null;
		Set<Point> puntos = getPuntosLinea( getUltimoPunto(), punto, trazo );
		for (Point aPunto : puntos) {
			if ( (aPunto.x >= 0) && (aPunto.y >= 0) ) {
				switch ( getMatriz()[aPunto.x][aPunto.y] ) {
					case PUNTO_VACIO:
						getMatriz()[aPunto.x][aPunto.y] = getNumeroDeTrazo();
						break;
					case PUNTO_PAPA:
						break;
					default:
						if ( (getNumeroDeTrazo() - getMatriz()[aPunto.x][aPunto.y]) > 10 ) {
							puntoChoque = aPunto;
						}
				}
				if ( puntoChoque != null ) {
					break;
				}
			}
		}
		setNumeroDeTrazo(getNumeroDeTrazo()+1);
		List<Point> extremos = trazo.getPuntos(punto);
		extremos.addAll( trazo.getPuntos( getUltimoPunto() ) );
		setChanged();
		notifyObservers( getRectangle( extremos ) );
		return puntoChoque;
	}
	
	/**
	 * Calcula todos los puntos de una linea. Segun un trazo.
	 * 
	 * @param ulitmoPunto
	 * @param punto
	 * @param trazo
	 * @return
	 */
	private Set<Point> getPuntosLinea(Point ultimoPunto, Point punto, Trazo trazo) {
		Set<Point> puntos = new LinkedHashSet<Point>();
		
		double x = punto.x - ultimoPunto.x;
		double y = punto.y - ultimoPunto.y;
		
		int cuadrante;
		if ( x >= 0 ) {
			if ( y <= 0 ) cuadrante = 1;
				else cuadrante = 4;
		} else {
			if ( y <= 0 ) cuadrante = 2;
				else cuadrante = 3;
		}
		
		double modulo = Math.sqrt(x*x+y*y);
		
		double angulo;
		if ( cuadrante == 1 ) {
			angulo = Math.acos( Math.abs(x)/modulo );
		} else if ( cuadrante == 2 ) {
			angulo = Math.PI - Math.acos( Math.abs(x)/modulo );
		} else if ( cuadrante == 3 ) {
			angulo = Math.PI + Math.acos( Math.abs(x)/modulo );
		} else {
			angulo = 2*Math.PI - Math.acos( Math.abs(x)/modulo );
		}
		
		for (double i=0 ; i < modulo ; i+=1) {
			Integer aX = new Double( Math.cos(angulo)*i ).intValue();
			Integer aY = new Double( -Math.sin(angulo)*i ).intValue();
			puntos.addAll( trazo.getPuntos( new Point(ultimoPunto.x+aX,ultimoPunto.y+aY) ) );
		}
		
		return puntos;
	}
	
	/**
	 * Obtione el menor rectangulo q contenga todos los puntos de la lista
	 * @param puntos
	 * @return
	 */
	private Rectangle getRectangle(List<Point> puntos) {
		int minX = ANCHO;
		int minY = ALTO;
		int maxX = 0;
		int maxY = 0;
		for (Point punto : puntos) {
			if ( minX > punto.x ) minX = punto.x;
			if ( minY > punto.y ) minY = punto.y;
			if ( maxX < punto.x ) maxX = punto.x;
			if ( maxY < punto.y ) maxY = punto.y;
		}
		return new Rectangle( minX, minY, maxX-minX+1, maxY-minY+1);
	}

	/**
	 * 
	 *
	 */
	public void limpiar() {
		setMatriz( new int[ANCHO][ALTO] );
	}

	/**
	 * @return the ulitmoPunto
	 */
	public Point getUltimoPunto() {
		return ultimoPunto;
	}

	/**
	 * @param ulitmoPunto the ulitmoPunto to set
	 */
	public void setUltimoPunto(Point ulitmoPunto) {
		this.ultimoPunto = ulitmoPunto;
	}

	/**
	 * @return the matriz
	 */
	public int[][] getMatriz() {
		return matriz;
	}

	/**
	 * @param matriz the matriz to set
	 */
	public void setMatriz(int[][] matriz) {
		this.matriz = matriz;
	}

	/**
	 * @return the numeroDeTrazo
	 */
	public Integer getNumeroDeTrazo() {
		return numeroDeTrazo;
	}

	/**
	 * @param numeroDeTrazo the numeroDeTrazo to set
	 */
	public void setNumeroDeTrazo(Integer numeroDeTrazo) {
		this.numeroDeTrazo = numeroDeTrazo;
	}
	
	/**
	 * Agrega los puntos de la papa a la matriz
	 * @param papa
	 */
	public void addPapa(Papa papa) {
		double radioCuadrado = Math.pow(papa.getRadio(), 2);
		for (int x = papa.getPosicion().x - papa.getRadio(); x < papa.getPosicion().x + papa.getRadio(); x++) {
			for (int y = papa.getPosicion().y - papa.getRadio(); y < papa.getPosicion().y + papa.getRadio(); y++) {
				if ( (x >= 0) && (y >= 0) ) {
					double distX = x-papa.getPosicion().x;
					double distY = y-papa.getPosicion().y;
					double distCuadrada = Math.pow(distX, 2) + Math.pow(distY, 2);
					if ( distCuadrada < radioCuadrado ) {
						getMatriz()[x][y] = PUNTO_PAPA;
					}
				}
			}
		}
	}
	
}
